package com.bilibili.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bilibili.dao.VideoMapper;
import com.bilibili.dao.VideoOperationMapper;
import com.bilibili.dao.VideoTagMapper;
import com.bilibili.dao.VideoViewMapper;
import com.bilibili.domain.PageResult;
import com.bilibili.domain.entity.UserInfo;
import com.bilibili.domain.entity.UserPreference;
import com.bilibili.domain.entity.Video;
import com.bilibili.domain.entity.VideoView;
import com.bilibili.domain.exception.ConditionException;
import com.bilibili.service.UserInfoService;
import com.bilibili.service.VideoOperationService;
import com.bilibili.service.VideoService;
import com.bilibili.utils.FastDFSUtil;
import com.bilibili.utils.IpUtil;
import eu.bitwalker.useragentutils.UserAgent;
import org.apache.mahout.cf.taste.common.TasteException;
import org.apache.mahout.cf.taste.impl.common.FastByIDMap;
import org.apache.mahout.cf.taste.impl.model.GenericDataModel;
import org.apache.mahout.cf.taste.impl.model.GenericPreference;
import org.apache.mahout.cf.taste.impl.model.GenericUserPreferenceArray;
import org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood;
import org.apache.mahout.cf.taste.impl.recommender.GenericUserBasedRecommender;
import org.apache.mahout.cf.taste.impl.similarity.UncenteredCosineSimilarity;
import org.apache.mahout.cf.taste.model.DataModel;
import org.apache.mahout.cf.taste.model.PreferenceArray;
import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood;
import org.apache.mahout.cf.taste.recommender.RecommendedItem;
import org.apache.mahout.cf.taste.recommender.Recommender;
import org.apache.mahout.cf.taste.similarity.UserSimilarity;

import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestParam;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
* @author www
* @description 针对表【t_video(视频投稿记录表)】的数据库操作Service实现
* @createDate 2024-06-16 22:13:51
*/
@Service
public class VideoServiceImpl extends ServiceImpl<VideoMapper, Video>
    implements VideoService {

    @Resource
    private VideoMapper videoMapper;
    @Resource
    private VideoTagMapper videoTagMapper;
    @Resource
    private FastDFSUtil fastDFSUtils;

    @Resource
    private UserInfoService userInfoService;
    @Resource
    private VideoViewMapper videoViewMapper;
    @Resource
    private VideoOperationMapper videoOperationMapper;

    @Override
    public void addVideos(Video video) {
        Date now = new Date();
        video.setCreateTime(now);
        videoMapper.insert(video);

//        List<VideoTag> videoTagList = video.getVideoTagList();
//        videoTagList.forEach(item ->{
//            item.setCreateTime(now);
//            item.setVideoId(video.getId());
//        });
//        videoTagMapper.batchAddVideoTags(videoTagList);
    }

    @Override
    public PageResult<Video> pageListVideos(Integer size, Integer no, String area) {
        if (size==0||no==0){
            throw new ConditionException("参数异常！");
        }
        Map<String,Object> param = new HashMap<>();
        param.put("start",(no-1)*size);
        param.put("limit",size);
        param.put("area",area);

        List<Video> list = new ArrayList<>();
        //查询满足条件的数量
        Integer total = videoMapper.pageCountVideos(param);

        if (total>0){
            list = videoMapper.pageListVideos(param);
        }
        return new PageResult<>(total,list);
    }

    @Override
    public void viewVideoOnlineBySlices(HttpServletRequest request, HttpServletResponse response, String url) throws Exception {
        fastDFSUtils.viewVideoOnlineBySlices(request,response,url);
    }

    @Override
    public Map<String, Object> getVideoDetails(@RequestParam Long videoId) {
        Video video = videoMapper.selectById(videoId);
        Long userId = video.getUserId();
        UserInfo userInfo = userInfoService.getByUserId(userId);
        Map<String,Object> result = new HashMap<>();
        result.put("video",video);
        result.put("userInfo",userInfo);
        return result;
    }

    @Override
    public void addVideoView(VideoView videoView, HttpServletRequest request) {
        Long userId = videoView.getUserId();
        Long videoId = videoView.getVideoId();
        //生成clientId
        String agent = request.getHeader("User-Agent");
        UserAgent userAgent = UserAgent.parseUserAgentString(agent);
        String clientId = String.valueOf(userAgent.getId());
        String ip = IpUtil.getIP(request);
        Map<String, Object> params = new HashMap<>();
        if(userId != null){
            params.put("userId", userId);
        }else{
            params.put("ip", ip);
            params.put("clientId", clientId);
        }
        Date now = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        params.put("today", sdf.format(now));
        params.put("videoId", videoId);
        //添加观看记录
        VideoView dbVideoView = videoViewMapper.getVideoView(params);
        if(dbVideoView == null){
            videoView.setIp(ip);
            videoView.setClientId(clientId);
            videoView.setCreateTime(new Date());
            videoViewMapper.insert(videoView);
        }
    }

    @Override
    public Integer getVideoViewCounts(Long videoId) {
        return videoViewMapper.getVideoViewCounts(videoId);
    }

    /**
     * 根据用户ID推荐视频。
     * 该方法首先获取所有用户喜好的视频数据，然后基于这些数据构建数据模型。
     * 接着，计算用户之间的相似度，并找出指定用户的邻居用户。
     * 最后，基于这些邻居用户的历史喜好，为指定用户推荐一个视频。
     *
     * @param userId 用户ID，用于推荐视频。
     * @return 包含推荐视频的列表。
     * @throws TasteException 如果推荐过程中出现错误。
     */
    @Override
    public List<Video> recommend(Long userId) throws TasteException {
        // 获取所有用户对视频的偏好信息
        List<UserPreference> list = videoOperationMapper.getAllUserPreference();
        // 根据偏好信息创建数据模型
        // 创建数据模型
        DataModel dataModel = this.createDataModel(list);
        // 使用余弦相似度计算用户之间的相似度
        // 获取用户相似程度
        UserSimilarity similarity = new UncenteredCosineSimilarity(dataModel);
        // 测试输出两个用户的相似度，用于调试
        System.out.println(similarity.userSimilarity(1797537989511409665l, 1797578472513937410l));
        // 设置每个用户最接近的邻居数量，这里设置为2
        // 获取用户邻居
        UserNeighborhood userNeighborhood = new NearestNUserNeighborhood(2, similarity, dataModel);
        // 获取指定用户的所有邻居用户ID
        long[] ar = userNeighborhood.getUserNeighborhood(userId);
        // 基于数据模型、用户相似度和用户邻居，创建用户基于邻域的推荐器
        // 构建推荐器
        Recommender recommender = new GenericUserBasedRecommender(dataModel, userNeighborhood, similarity);
        // 为指定用户推荐一个视频，返回推荐的视频列表
        // 推荐视频
        List<RecommendedItem> recommendedItems = recommender.recommend(userId, 5);
        // 从推荐的视频列表中提取视频ID
        List<Long> itemIds = recommendedItems.stream().map(RecommendedItem::getItemID).collect(Collectors.toList());
        // 如果推荐的视频ID列表为空，则抛出异常
        if (itemIds.isEmpty()){
            throw new ConditionException("推荐列表为空！");
        }
        // 根据提取的视频ID，从数据库中查询对应的视频信息，并返回推荐的视频列表
        return videoMapper.selectBatchIds(itemIds);
    }


    private DataModel createDataModel(List<UserPreference> userPreferenceList) {
        FastByIDMap<PreferenceArray> fastByIdMap = new FastByIDMap<>();
        Map<Long, List<UserPreference>> map = userPreferenceList.stream().collect(Collectors.groupingBy(UserPreference::getUserId));
        Collection<List<UserPreference>> list = map.values();
        for(List<UserPreference> userPreferences : list){
            GenericPreference[] array = new GenericPreference[userPreferences.size()];
            for(int i = 0; i < userPreferences.size(); i++){
                UserPreference userPreference = userPreferences.get(i);
                GenericPreference item = new GenericPreference(userPreference.getUserId(), userPreference.getVideoId(), userPreference.getValue());
                array[i] = item;
            }
            fastByIdMap.put(array[0].getUserID(), new GenericUserPreferenceArray(Arrays.asList(array)));
        }
        return new GenericDataModel(fastByIdMap);
    }

}




